#include <glm/glm.hpp>
#include <glm/gtc/matrix_projection.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "Material.hpp"
#include <string>
#include <iostream>


#ifndef __HEIGHTMAP_HPP__
#define __HEIGHTMAP_HPP__

using namespace std;
using namespace glm;

/** class to create an manage ground defined by an heightmap */
class HeightMap {
	private:
		/** width of the image used to define the heightmap */
		int imageWidth;
		/** height of the image used to define the heightmap */
		int imageHeight;

		/** width and height of the map (number of cells) */
		int mapWidth, mapHeight;

		/** scaleFactor apply to the image height (0.0 to 1.0) 
		  * for defining the heightmap (0.0 to scaleFactor) */
		int scaleFactor;

		/** array for storing heights for each point of the heightmap 
		  *  m_heightMap[mapWidth][mapHeight] */
		GLfloat* m_heightMap;

		/** */
		bool* m_collisionMap;

		/** array for storing normal vector for each point of the heightmap 
		  * m_normapMap[mapWidth][mapHeight] */
		vec3* m_normalMap;

		/** array for storing tangent vector for each point of the heightmap, (in a
		  * a future release tangent vector will be used for bump mapping texture) */
		vec3* m_tangentMap;

		/** variable to store material for map rendering */
		Material* m_material;

		/** variable for storing shader texture sampler identifier*/
		GLuint m_grassTex_id, m_sandTex_id, m_seaTex_id, m_tex_id, m_shadowMapTexture_id;

		/** openGL uint identifier to store uniform mat4 id for sending projection matrix
		  *	to a shader for shadow calculation by depth comparison (UNUSED) */
		GLuint m_shadowProjectionMatrix_id;

		/** offset of the map */
		vec3 m_offset;

		/** dimension of a elementary cell of the ground*/
		float caseDimension;

		/** pointer to store shadow projection matrix as a float array*/
		GLfloat* m_shadowProjectionMatrix;

		/** point coords and texture coords buffer*/
		GLuint* m_buffers;

	public:
		/** HeightMap constructor : set array to NULL pointer and give
		  * the default offset */
		HeightMap();

		/** HeightMap destructor : delete arrays (freeing memory) */
		~HeightMap(); 

		/** defining shadow projection matrix */
		void setShadowProjectionMatrix(GLfloat* mat);

		/** function to retrieve map offset
		 * return vec3 offset */
		vec3 getOffset() { return m_offset;};

		/** function to load a map through an  image name
		 *  param[in] filename name of the image name
		 *  param[in] wX width of the ground heightmap array to be created
		 *  param[in] wY height of the ground heightmap array to be created
		 */
		void loadMap(string filename, int wX, int wY);

		/** static value for storing pointer to the current heightmap
		  * accessible as a singleton */
		static HeightMap* mainHeightMap;

		/** function to change texture of the map
		 * param[in] textureName new file to be loaded as a texture
		 * param[in] id   texture to be changed
		 * 0 for base, 1 for grass, 2 for sand and 3 for sea
		 */
		void changeTexture(string textureName, int id = 0); 

		/** function to redefine map offset 
		 *	param[in] ox  x coords of the new offset
		 *  param[in] oy  y coords of the new offset
		 *  param[in] oz  z coords of the new offset
		 */
		void setOffset(float ox, float oy, float oz);


		/** define the height map offset */
		void setOffset(glm::vec3 off_) {
			setOffset(off_.x, off_.y, off_.z);
		}	


		/** function to define a Material for the ground
		 *  param[in] textureName   name of the texture to be loaded in the material
		 */
		void loadMaterial(string textureName); 

		/**
		 * rendering function to display ground on screen
		 */
		void render();

		/** function to set normal of vertex with integer coords (x,y)
		 * the normal is set with the glNormal() function
		 * param[in] x coords of vertex
		 * param[in] y coords of vertex 
		 */
		void setNormal(int x, int y);

		/** function to compute normal for terrain, stored in normalMap
		 * param[in] vertexIndexX x coords for normal computation
		 * param[in] vertexIndexY y coords for normal computation
		 */
		vec3 normalComp(int vertexIndexX, int vertexIndexY);


		/** function to compute the height of a point in the map
		 * param[in] x X coords of the point
		 * param[in] y Y coords of the point
		 * return float computed height of the arg point
		 */
		float getHeight(float x, float y);

		/** delete openGL buffers and recreate them based on current heightMap */
		void recreateBuffer();

		/** function to create buffer objects */
		void createBuffer();

		/** function to render heightmap using buffers */
		void drawByBuffer();

		/** function to render heightmap without using buffer */
		void drawDirectly();

		/** creating collision map from height map */
		void createCollisionMap();

		/** does a point is under or over seas ? */
		bool getCollision(float x, float y);

		/** modifying heightmap on the fly */
		void modifyHeight(glm::vec2 coords_, float factor_ = 0.1);

		/** save the heightmap as a image file */
		void save();

		/** return a pointer to the material used for map rendering */
		Material* getMaterial() { return m_material;};
};


#endif
